/*
 * @(#)Socket.java	1.31 2001/12/12
 *
 * 版权所有2002 Sun Microsystems，股份有限公司。保留所有权利。SUN专有/机密。使用需遵守许可条款。
 */

package java.net;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.io.InterruptedIOException;

/**
 * 此类实现了客户端套接字(也称为“套接字”)。套接字是两台机器之间通信的终结点。
 * <p>
 * 套接字的实际工作由 SocketImpl 类的实例执行。
 * 通过更改创建套接字实现的套接字工厂，应用程序可以将自身配置为创建适合本地防火墙的套接字。
 *
 * @author  unascribed
 * @version 1.31, 12/12/01
 * @see     Socket#setSocketImplFactory(SocketImplFactory)
 * @see     SocketImpl
 * @since   JDK1.0
 */
public  
class Socket {
    /**
     * The implementation of this Socket.
     */
    SocketImpl impl;

    /**
     * Creates an unconnected socket, with the
     * system-default type of SocketImpl.
     *
     * @since   JDK1.1
     */
    protected Socket() {
	impl = (factory != null) ? factory.createSocketImpl() : 
	    new PlainSocketImpl();
    }

    /**
     * Creates an unconnected Socket with a user-specified
     * SocketImpl.
     * <P>
     * The <i>impl</i> parameter is an instance of a <B>SocketImpl</B> 
     * the subclass wishes to use on the Socket. 
     *
     * @since   JDK1.1
     */
    protected Socket(SocketImpl impl) throws SocketException {
	this.impl = impl;
    }

    /** 
     * 创建流套接字并将其连接到命名主机上的指定端口号。
     * <p>
     * 如果应用程序指定了服务器套接字工厂，则调用该工厂的 createSocketImpl 方法来创建实际的套接字实现。
	 * 否则，将创建一个“普通”套接字。
     *
     * @param      host   the host name.
     * @param      port   the port number.
     * @exception  IOException  if an I/O error occurs when creating the socket.
     * @see        Socket#setSocketImplFactory(SocketImplFactory)
     * @see        SocketImpl
     * @see        SocketImplFactory#createSocketImpl()
     * @since      JDK1.0
     */
    public Socket(String host, int port)
	throws UnknownHostException, IOException
    {
	this(InetAddress.getByName(host), port, null, 0, true);
    }

    /** 
     * 创建流套接字，并将其连接到位于指定IP地址的指定端口号。
     * <p>
     * 如果应用程序指定了套接字工厂，则调用该工厂的<code>createSocketImpl</code>方法来创建实际的套接字实现。
	 * 否则，将创建一个“普通”套接字。
     *
     * @param      address   the IP address.
     * @param      port      the port number.
     * @exception  IOException  if an I/O error occurs when creating the socket.
     * @see        Socket#setSocketImplFactory(SocketImplFactory)
     * @see        SocketImpl
     * @see        SocketImplFactory#createSocketImpl()
     * @since      JDK1.0
     */
    public Socket(InetAddress address, int port) throws IOException {
	this(address, port, null, 0, true);
    }

    /** 
     * 创建套接字并将其连接到指定远程端口上的指定远程主机。套接字还将把()绑定到提供的本地地址和端口。
	 *
     * @param host the name of the remote host
     * @param port the remote port
     * @param localAddr the local address the socket is bound to
     * @param localPort the local port the socket is bound to
     * @since   JDK1.1
     */
    public Socket(String host, int port, InetAddress localAddr, 
		  int localPort) throws IOException {
	this(InetAddress.getByName(host), port, localAddr, localPort, true);
    }

    /**
	 * 创建套接字并将其连接到指定远程端口上的指定远程地址。套接字还将把()绑定到提供的本地地址和端口。
     * @param address the remote address
     * @param port the remote port
     * @param localAddr the local address the socket is bound to
     * @param localPort the local port the socket is bound to
     * @since   JDK1.1
     */
    public Socket(InetAddress address, int port, InetAddress localAddr, 
		  int localPort) throws IOException {
		      this(address, port, localAddr, localPort, true);
    };		     

    /**
     * 创建流套接字并将其连接到命名主机上的指定端口号。
     * <p>
     * 如果stream参数为<code>true</code>，这将创建一个流套接字。
	 * 如果STREAM参数为<code>FALSE</code>，则创建数据报套接字。
     * <p>
     * 如果应用程序指定了服务器套接字工厂，则调用该工厂的<code>createSocketImpl</code>方法来创建实际的套接字实现。
	 * 否则，将创建一个“普通”套接字。
     *
     * @param      host     the host name.
     * @param      port     the port number.
     * @param      stream   a <code>boolean</code> indicating whether this is
     *                      a stream socket or a datagram socket.
     * @exception  IOException  if an I/O error occurs when creating the socket.
     * @see        Socket#setSocketImplFactory(SocketImplFactory)
     * @see        SocketImpl
     * @see        SocketImplFactory#createSocketImpl()
     * @since      JDK1.0
     * @deprecated Use DatagramSocket instead for UDP transport.
     */
    public Socket(String host, int port, boolean stream) throws IOException {
	this(InetAddress.getByName(host), port, null, 0, stream);
    }

    /**
     * 创建套接字并将其连接到位于指定IP地址的指定端口号。
     * <p>
     * 如果stream参数为<code>true</code>，这将创建一个流套接字。
	 * 如果STREAM参数为<code>FALSE</code>，则创建数据报套接字。
     * <p>
     * 如果应用程序指定了服务器套接字工厂，则调用该工厂的<code>createSocketImpl</code>方法来创建实际的套接字实现。
	 * 否则，将创建一个“普通”套接字。
     *
     * @param      address   the IP address.
     * @param      port      the port number.
     * @param      stream    if <code>true</code>, create a stream socket;
     *                       otherwise, create a datagram socket.
     * @exception  IOException  if an I/O error occurs when creating the socket.
     * @see        Socket#setSocketImplFactory(SocketImplFactory)
     * @see        SocketImpl
     * @see        SocketImplFactory#createSocketImpl()
     * @since      JDK1.0
     * @deprecated Use DatagramSocket instead for UDP transport.
     */
    public Socket(InetAddress host, int port, boolean stream) throws IOException {
	this(host, port, null, 0, stream);
    }
    
    private Socket(InetAddress address, int port, InetAddress localAddr, 
		  int localPort, boolean stream) throws IOException {
	this();

	if (port < 0 || port > 0xFFFF) {
	    throw new IllegalArgumentException("port out range:"+port);
	}

	if (localPort < 0 || localPort > 0xFFFF) {
	    throw new IllegalArgumentException("port out range:"+localPort);
	}

	SecurityManager security = System.getSecurityManager();
	if (security != null) {
	    security.checkConnect(address.getHostAddress(), port);
	}

	try {
	    impl.create(stream); 
	    if (localAddr != null || localPort > 0) {
		if (localAddr == null) {
		    localAddr = InetAddress.anyLocalAddress;
		}
		impl.bind(localAddr, localPort);
	    }
	    impl.connect(address, port);
	} catch (SocketException e) {
	    impl.close();
	    throw e;
	}
    }

    /**
     * Returns the address to which the socket is connected.
     *
     * @return  the remote IP address to which this socket is connected.
     * @since   JDK1.0
     */
    public InetAddress getInetAddress() {
	return impl.getInetAddress();
    }

    /**
     * Gets the local address to which the socket is bound.
     *
     * @since   JDK1.1
     */
    public InetAddress getLocalAddress() {
	InetAddress in = null;
	try {
	    in = (InetAddress) impl.getOption(SocketOptions.SO_BINDADDR);
	} catch (Exception e) {
	    in = InetAddress.anyLocalAddress; // "0.0.0.0"
	}
	return in;
    }

    /**
     * Returns the remote port to which this socket is connected.
     *
     * @return  the remote port number to which this socket is connected.
     * @since   JDK1.0
     */
    public int getPort() {
	return impl.getPort();
    }

    /**
     * Returns the local port to which this socket is bound.
     *
     * @return  the local port number to which this socket is connected.
     * @since   JDK1.0
     */
    public int getLocalPort() {
	return impl.getLocalPort();
    }

    /**
     * Returns an input stream for this socket.
     *
     * @return     an input stream for reading bytes from this socket.
     * @exception  IOException  if an I/O error occurs when creating the
     *               input stream.
     * @since      JDK1.0
     */
    public InputStream getInputStream() throws IOException {
	return impl.getInputStream();
    }

    /**
     * Returns an output stream for this socket.
     *
     * @return     an output stream for writing bytes to this socket.
     * @exception  IOException  if an I/O error occurs when creating the
     *               output stream.
     * @since      JDK1.0
     */
    public OutputStream getOutputStream() throws IOException {
	return impl.getOutputStream();
    }

    /**
     * Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm).
     *
     * @since   JDK1.1
     */
    public void setTcpNoDelay(boolean on) throws SocketException {
	impl.setOption(SocketOptions.TCP_NODELAY, new Boolean(on));
    }

    /**
     * Tests if TCP_NODELAY is enabled.
     *
     * @since   JDK1.1
     */
    public boolean getTcpNoDelay() throws SocketException {
	return ((Boolean) impl.getOption(SocketOptions.TCP_NODELAY)).booleanValue();
    }

    /**
     * Enable/disable SO_LINGER with the specified linger time.  
     *
     * @since   JDK1.1
     */
    public void setSoLinger(boolean on, int val) throws SocketException {
	if (!on) {
	    impl.setOption(SocketOptions.SO_LINGER, new Boolean(on));
	} else {
	    impl.setOption(SocketOptions.SO_LINGER, new Integer(val));
	}
    }

    /**
     * Returns setting for SO_LINGER. -1 returns implies that the
     * option is disabled.
     *
     * @since   JDK1.1
     */
    public int getSoLinger() throws SocketException {
	Object o = impl.getOption(SocketOptions.SO_LINGER);
	if (o instanceof Integer) {
	    return ((Integer) o).intValue();
	} else {
	    return -1;
	}
    }

    /**
     * 启用/禁用具有指定超时的SO_TIMEOUT，以毫秒为单位。
	 * 当此选项设置为非零超时时，与此套接字关联的InputStream上的Read()调用将仅在此时间量内阻塞。
	 * 如果超时到期，则会引发<b>java.io.InterruptedIOException</b>，但套接字仍然有效。
	 * 必须在进入阻止操作之前启用<b></b>选项才能生效。
	 * 超时必须大于0。
     * 零的超时被解释为无限超时。
     *
     * @since   JDK 1.1
     */
    public synchronized void setSoTimeout(int timeout) throws SocketException {
	impl.setOption(SocketOptions.SO_TIMEOUT, new Integer(timeout));
    }

    /**
     * 返回SO_TIMEOUT的设置。返回0表示该选项已禁用(即无限超时)。
     *
     * @since   JDK1.1
     */
    public synchronized int getSoTimeout() throws SocketException {
	Object o = impl.getOption(SocketOptions.SO_TIMEOUT);
	/* extra type safety */
	if (o instanceof Integer) {
	    return ((Integer) o).intValue();
	} else {
	    return 0;
	}
    }

    /**
     * Closes this socket. 
     *
     * @exception  IOException  if an I/O error occurs when closing this socket.
     * @since      JDK1.0
     */
    public synchronized void close() throws IOException {
	impl.close();
    }

    /**
     * Converts this socket to a <code>String</code>.
     *
     * @return  a string representation of this socket.
     * @since   JDK1.0
     */
    public String toString() {
	return "Socket[addr=" + impl.getInetAddress() +
	    ",port=" + impl.getPort() + 
	    ",localport=" + impl.getLocalPort() + "]";
    }

    /**
     * The factory for all client sockets.
     */
    private static SocketImplFactory factory;

    /**
     * 设置应用程序的客户端套接字实现工厂。工厂只能指定一次。
     * <p>
     * 当应用程序创建新的客户端套接字时，将调用套接字实现工厂的<code>createSocketImpl</code>方法来创建实际的套接字实现。
     *
     * @param      fac   the desired factory.
     * @exception  IOException  if an I/O error occurs when setting the
     *               socket factory.
     * @exception  SocketException  if the factory is already defined.
     * @see        SocketImplFactory#createSocketImpl()
     * @since      JDK1.0
     */
    public static synchronized void setSocketImplFactory(SocketImplFactory fac)
	throws IOException
    {
	if (factory != null) {
	    throw new SocketException("factory already defined");
	}
	SecurityManager security = System.getSecurityManager();
	if (security != null) {
	    security.checkSetFactory();
	}
	factory = fac;
    }
}
